BearZPY Blog

Hi, nice to meet you

BearZPY's avatar BearZPY

Android ZXing 条码扫描

Android ZXing 条码扫描

Android 应用经常会遇到扫码的需求,目前使用的库主要是 ZXing 和 Zbar,本文主要讲述的是 ZXing 的使用。ZXing 官方例子是非常庞大的,考虑的方面也比较全,初学者理解上会有一定的困难,本文主要是帮助初学者理解 ZXing 库的使用。

确定扫码功能的核心

扫码功能可以很简单的分为拍摄和解码两大部分,拍摄功能很容易理解,就是拍摄一帧包含二维码的图像,而解码又分两部分,即对一帧图像里面条码的定位,以及对条码内容的解析。ZXing 已经帮我们完成了解码的两个部分,这是扫码功能的核心。而拍摄的功能可以由我们自行定制,我们需要做的只是提供一帧帧图像给 ZXing 的解码器。

拍摄功能

Android 实现拍摄涉及到两个组件(Camera,和 View),其中最核心的是 Camera 的配置,View 主要负责显示拍摄的内容(在前置扫码需求下,View 甚至是可以不可见的),这部分都不涉及到 ZXing 的使用,这里只讲解最基本的的功能实现,取景框等放到最后理解。

Camera 的基本参数配置

  • 选择使用的摄像头
  • 设置拍摄图像的显示组件(SurfaceHolder)
  • 选择对焦的模式
  • 确定预览图像的大小(eg:1280*720)
  • 设置摄像头的预览方向(成像方向)
  • 设置单次预览图像的回调
public void init(SurfaceHolder holder) {

    if ( mCamera == null ) {
        mCamera = Camera.open();
        try {
            mCamera.setPreviewDisplay( holder );
        } catch (IOException e) {
            mCamera.release();
            mCamera = null;
        }
        if (mCamera == null) {
            Log.e(LOG_TAG,"surfaceChanged: mCamera == null");
            if (callback != null) {
                callback.onCameraOpenFailed();
            }
            return;
        }
        mCameraParameters = mCamera.getParameters();
        mCameraParameters.setFocusMode( Camera.Parameters.FOCUS_MODE_AUTO );
        mCameraParameters.setPreviewSize( 1280, 720 );
        cameraResolution.x = 1280;
        cameraResolution.y = 720;
        mCamera.setParameters( mCameraParameters );
        mCamera.setDisplayOrientation( 90 );
        mCamera.startPreview();
        mCamera.cancelAutoFocus();
        mCamera.setAutoFocusMoveCallback( new AutoFocusMoveCallbackImp() );
        previewCallbackImp = new PreviewCallbackImp();
        // Camera 返回一帧的预览图像,在此回调中,进行图像解码
        mCamera.setOneShotPreviewCallback(previewCallbackImp);
    }
}

public void destroy() {
    if (mCamera != null) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
}

View 的参数配置

显示拍摄内容需要使用控件 SurfaceView,这个控件需要通过 SurfaceHolder 来与摄像头连接起来。当需要拍摄时创建 SurfaceView,此时 SurfaceHolder 需要打开摄像头进行拍摄。当拍摄结束时销毁 SurfaceView,,此时 SurfaceHolder 关闭摄像头预览并释放资源。

// 创建 SurfaceView 后添加 SurfaceHolder 以显示拍摄内容
surfaceView.getHolder().addCallback(new SurfaceHolderCallbackImp());

// 随着 SurfaceView 的生命周期控制摄像头资源的使用和释放
private class SurfaceHolderCallbackImp implements SurfaceHolder.Callback {

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        CameraManager.getInstance().init(holder);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        CameraManager.getInstance().destroy();
    }
}

解码功能

解码功能就完全是 ZXing 库提供的了,我们需要按照我们自己的需求初始化 ZXing 的解码器,并将图像数据以正确的格式传给解码器。

ZXing 解码器配置

  • 配置解码的格式(只针对需要的格式解码可以提高速度)
  • 设置特征点回调(特征点可以选择是否需要现在图像上)
  • 配置字符集,配置解码精度等等(可选)
// 多格式解码器,如果只处理一种格式完全可以使用对应的解码器,以提高速度
private MultiFormatReader qrcodeReader = null;

public void initQRCodeReader() {
    Vector<BarcodeFormat> decodeFormats = new Vector<>();

    decodeFormats.add(BarcodeFormat.QR_CODE);
    decodeFormats.add(BarcodeFormat.DATA_MATRIX);

    Hashtable<DecodeHintType, Object> hints = new Hashtable<>(3);
    hints.put( DecodeHintType.POSSIBLE_FORMATS, decodeFormats );
    hints.put( DecodeHintType.NEED_RESULT_POINT_CALLBACK, new ResultPointCallbackImp() );

    qrcodeReader = new MultiFormatReader();
    qrcodeReader.setHints( hints );
}

拍摄图像处理

拍摄完成的图像并不是可以直接送给解码器的,我们要对其进行提取亮度,二值化操作才行。在处理的过程中,我们可以对图像进行裁剪,配合裁剪可以实现取景框的功能。Android 相机预览默认格式是 yuv420sp,编码成 YUV 的所有像素格式里,yuv420sp 占用的空间是最小的。我们通过 PlanarYUVLuminanceSource 来提取亮度。提取亮度之后需要进行二值化操作,此处常见的二值化方法有两种,分别是 HybridBinarizer 和 GlobalHistogramBinarizer。其中 HybridBinarizer 继承自 GlobalHistogramBinarizer,识别精度要比 GlobalHistogramBinarizer 好,但是速度比其低。实际使用中应该根据具体需求去选择。

public void handlePreviewFrame(byte[] data ,int width, int height) {
    // 提取亮度,此处可选择是否做裁剪
    PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource( data, width, height,
            0, 0, width, height, false );

    // 配置二值化算法
    BinaryBitmap bitmap = new BinaryBitmap( new HybridBinarizer( source ) );
    //BinaryBitmap bitmap = new BinaryBitmap( new GlobalHistogramBinarizer( source ) );

    // 解码
    Result result = null;
    if ( qrcodeReader != null ) {
        try {
            result = qrcodeReader.decodeWithState( bitmap );
        } catch (NotFoundException e) {
            //LogError( "NotFoundException: " + e );
        } finally {
            qrcodeReader.reset();
        }
    }
    if ( result != null ) {
        // 扫描完成解码成功
        if( scannerCallback != null ) {
            scannerCallback.onScanSuccess(result);
        }
    }
}

ZXing 官方例子补充

ZXing 的官方库中考虑了很多内容,所以在上述基本功能上添加了如下内容,只要先理解了核心功能,这些拓展功能的理解就相对容易多了。

* 长时间无活动自动销毁机制,避免耗电 InactivityTimer
* 扫描完成蜂鸣器提醒机制 BeepManager
* 设置取景框,裁剪无用区域 ViewfinderView
* 首次使用确定最佳的聚焦模式并保存
* 首次使用计算最佳摄像头预览大小并保存
* 扫描发现的特征点显示在屏幕上
* 从相册中获取图片扫码(RGB格式)